package org.vectrics.batch;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.log4j.Logger;
import org.vectrics.SystemException;
import org.vectrics.common.util.DateSync;
import org.vectrics.domain.Domain;
import org.vectrics.hibernate.ThreadProperties;
import org.vectrics.hibernate.TransactionToken;
import org.vectrics.provider.CoreServiceLocator;
import org.vectrics.user.Person;

/**
 * @author MMoore
 */
abstract public class Batch implements Runnable {
	private transient final Logger log = Logger.getLogger(this.getClass());
	private BatchInfo batchInfo = new BatchInfo();
	private boolean terminate = false;
	private boolean pause = false;
	private static Set<Class> runningBatches = new HashSet<Class>();
	private Domain domain = null;
	
	abstract protected String getDescription();
	abstract protected String getBatchTypeCode();
	abstract protected void execute(Domain domain);
	abstract protected boolean logOnlyErrors();
	abstract protected boolean executeForEachDomain();
	
	
	public Batch(Domain domain) {
		this.domain = domain;
	}
	
	public void pause() {
		pause = true;
	}
	
	public void resume() {
		pause = false;
	}
	
	public boolean isPaused() {
		return(pause);
	}
	
	 protected boolean isBatchRunning() {
 		boolean running = false; 
	 	synchronized(runningBatches) {
	 		Iterator iter = runningBatches.iterator();
	 		while (iter.hasNext()) {
	 			Class batchClass = (Class)iter.next();
	 			log.debug("Running batch: " + batchClass.getName());
	 		}
	 		running = runningBatches.contains(this.getClass());
	 		log.debug("Batch for class: " + this.getClass().getName() + "  Running: " + running  + "  Number of batches running: " + runningBatches.size());
	 	}
	 	return(running);
	}

	abstract protected boolean persistRunResults();
	
	/*
	public final void run() {
		Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
		if (Batch.applicationInformation == null)
			throw new SystemException("Static BeanFactory not set");
		ThreadProperties.setApplicationContext(applicationInformation);
		
		boolean rollback = false;
		try {
			TransactionToken txToken = ThreadProperties.beginTransaction();
			ThreadProperties.setPerson(this.getOwner());		
			if (isBatchRunning() == false) {
				ThreadProperties.assertTransaction();
				ThreadProperties.getSession().clear();
				
				log.debug("Batch running = " + this.isBatchRunning());
				synchronized(runningBatches) {
					runningBatches.add(this.getClass());
				}
				try {
					ThreadProperties.assertTransaction();
					log.debug("Starting batch: " + this.getDescription());
					if ((this.persistRunResults()) || (this.logOnlyErrors()))
						recordBatchStart();
					try {
						Iterator doIter = CoreServiceLocator.getSystemService().findAllDomains().iterator();
						while (doIter.hasNext()) {
							Domain domain = (Domain)doIter.next();
							try {
								execute(domain);
								ThreadProperties.forcedCommit();
							} catch (Exception ex) {
								log.error("Exception running batch for domain = " + domain.getId() + 
										"  Batch = " + this.getDescription() + "  Batch.id = " + this.getId(), ex);
								rollback = true;
								ThreadProperties.rollbackTransaction();
								if ((this.persistRunResults()) || (this.logOnlyErrors())) {
									recordBatchFailed(ex);
									logError("Exception running batch for domain " + domain.getId()
											+ "  Error: " + ex.getLocalizedMessage());
								}
							}
						}

						if (this.getTerminated()) {
							log.debug("Batch terminated");
							if (this.persistRunResults())
								this.recordBatchTerminated();
							else
								log.warn("Batch terminated: " + this.getDescription());
						} else {
							log.debug("Batch ended: " + this.getDescription());
							if ((this.persistRunResults()) || (this.logOnlyErrors())) {
								recordBatchEnd();
							}
						}
						//HibernateSessionStore.forcedCommitAndNewSession();
					} finally {
					}
				} catch (Exception ex) {
					log.error("Exception in batch", ex);
					throw new SystemException("Exception in batch", ex);
				} finally {
					synchronized(runningBatches) {
						runningBatches.remove(this.getClass());
					}
					if (this.isBatchRunning()) {
						throw new SystemException("Unexpected condition, after batch ended, logic shows it is still running.");
					}
					log.debug("Batch finished: " 
							+ "\r\n - id   = " + this.getId()
							+ "\r\n - desc = " + this.getDescription()
							);
				}
			} else {
				log.info("Batch has other instance running: " + this.getDescription());
			}
			if (rollback == false)
				ThreadProperties.commitTransaction(txToken);
		} catch (Exception ex) {
			log.error("Exception running batch", ex);
			if (rollback == false)
				ThreadProperties.rollbackTransaction();
		} finally {
			ThreadProperties.closeSession();
		}
	}
	*/

	public final void run() {
		Person person = ThreadProperties.getPerson();
		ThreadProperties.forcedCommitAndClose();
		
		Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
		
		boolean rollback = false;
		try {
			
			TransactionToken txToken = ThreadProperties.beginTransaction();
			
			if (person==null) {
				ThreadProperties.setPerson(this.getOwner());		
			} else {
				ThreadProperties.setPerson(person);
			}
			if (isBatchRunning() == false) {
				ThreadProperties.assertTransaction();
				//ThreadProperties.getSession().clear();
				
				log.debug("Batch running = " + this.isBatchRunning());
				synchronized(runningBatches) {
					runningBatches.add(this.getClass());
				}
				try {
					ThreadProperties.assertTransaction();
					log.debug("Starting batch: " + this.getDescription());
					if ((this.persistRunResults()) || (this.logOnlyErrors()))
						recordBatchStart();
					try {
						try {
							execute(domain);
							ThreadProperties.forcedCommit();
						} catch (Exception ex) {
							log.error("Exception running batch for domain = " + domain.getId() + 
									"  Batch = " + this.getDescription() + "  Batch.id = " + this.getId(), ex);
							rollback = true;
							ThreadProperties.rollbackTransaction();
							if ((this.persistRunResults()) || (this.logOnlyErrors())) {
								recordBatchFailed(ex);
								logError("Exception running batch for domain " + domain.getId()
										+ "  Error: " + ex.getLocalizedMessage());
							}
						}

						if (this.getTerminated()) {
							log.debug("Batch terminated");
							if (this.persistRunResults())
								this.recordBatchTerminated();
							else
								log.warn("Batch terminated: " + this.getDescription());
						} else {
							log.debug("Batch ended: " + this.getDescription());
							if ((this.persistRunResults()) || (this.logOnlyErrors())) {
								recordBatchEnd();
							}
						}
						//HibernateSessionStore.forcedCommitAndNewSession();
					} finally {
					}
				} catch (Exception ex) {
					log.error("Exception in batch", ex);
					throw new SystemException("Exception in batch", ex);
				} finally {
					synchronized(runningBatches) {
						runningBatches.remove(this.getClass());
					}
					if (this.isBatchRunning()) {
						throw new SystemException("Unexpected condition, after batch ended, logic shows it is still running.");
					}
					log.debug("Batch finished: " 
							+ "\r\n - id   = " + this.getId()
							+ "\r\n - desc = " + this.getDescription()
							);
				}
			} else {
				log.info("Batch has other instance running: " + this.getDescription());
			}
			if (rollback == false)
				ThreadProperties.commitTransaction(txToken);
		} catch (Exception ex) {
			log.error("Exception running batch", ex);
			if (ThreadProperties.isTransactionInProgress())
				ThreadProperties.rollbackTransaction();
		} finally {
			if (ThreadProperties.isTransactionInProgress())
				ThreadProperties.forcedCommitAndClose();
			else
				ThreadProperties.closeSession();
		}
	}
	
	
	
	
	protected Person getOwner() {
		ThreadProperties.setSystemUser(true);
		Person batchPerson = CoreServiceLocator.getConfiguration().getBatchPerson();
		ThreadProperties.setSystemUser(false);
		if (batchPerson == null)
			throw new SystemException("BatchPerson is null");
		return(batchPerson);
	}
	
	public final void terminate() {
		terminate = true;
		this.log("Terminate requested on this batch");
	}

	public boolean getTerminated() {
		return(terminate);
	}
	
	/**
	 * Log the start of the batch (log table in database).
	 */
	private void recordBatchStart() {
		log.debug("Starting batch job: " + this.getDescription());
		batchInfo.setDateStarted(DateSync.getCurrentTime());
		batchInfo.setDescription(getDescription());
		batchInfo.setBatchTypeCode(this.getBatchTypeCode());
		batchInfo.setClassName(this.getClass().getName());
		batchInfo.setStatus("started");
		if ((batchInfo.getBatchTypeCode() == null) && (batchInfo.getBatchTypeCode().trim().length() == 0)) {
			throw new SystemException("Batch type code is null or blanks - must have value");
		}
		CoreServiceLocator.getSystemService().saveBatchInfo(batchInfo);
		ThreadProperties.forcedCommitAndNewTransaction();
		this.log("Batch Started");
	}

	
	/**
	 * Log the end of the batch (log table in database).
	 */
	private void recordBatchEnd() {
		ThreadProperties.forcedCommitAndNewTransaction();
		batchInfo = CoreServiceLocator.getSystemService().findBatchInfo(batchInfo.getId());
		if (batchInfo == null) {
			throw new SystemException("Could not find batch info for id = " + batchInfo.getId());
		}
		batchInfo.setDateEnded(DateSync.getCurrentTime());

		if (batchInfo.getStatus().equals("error") == false)
			batchInfo.setStatus("finished");
		
		double seconds = (batchInfo.getDateEnded().getTime() - batchInfo.getDateStarted().getTime())/1000.0;
		CoreServiceLocator.getSystemService().updateBatchInfo(batchInfo);
		ThreadProperties.forcedCommit();
		this.log("Batch Finished");
		
		if (log.isDebugEnabled()) {
			log.debug("Batch Finished: " + this.getDescription() + "  Time: " + seconds);
		}
		ThreadProperties.forcedCommit();
	}
	
	/**
	 * Log that the batch terminated.
	 */
	private void recordBatchTerminated() {
		ThreadProperties.forcedCommit();
		batchInfo = CoreServiceLocator.getSystemService().findBatchInfo(batchInfo.getId());
		batchInfo.setDateEnded(DateSync.getCurrentTime());
		batchInfo.setStatus("terminated");
		CoreServiceLocator.getSystemService().updateBatchInfo(batchInfo);
		ThreadProperties.forcedCommit();
		this.log("Batch Finished");
	}

	
	private void recordBatchFailed(Exception ex) {
		if ((this.persistRunResults()) || (this.logOnlyErrors())) {
			ThreadProperties.rollbackTransaction();
			ThreadProperties.beginTransaction();
			batchInfo = CoreServiceLocator.getSystemService().findBatchInfo(batchInfo.getId());
			batchInfo.setDateEnded(DateSync.getCurrentTime());
			batchInfo.setStatus("error");
			CoreServiceLocator.getSystemService().updateBatchInfo(batchInfo);
			ThreadProperties.forcedCommit();
		}
	}
	
	public Long getId() {
		return(batchInfo.getId());
	}
	
	protected final void logError(String message) {
		if ((this.persistRunResults()) || (this.logOnlyErrors())) {
			ThreadProperties.forcedCommit();
			batchInfo = CoreServiceLocator.getSystemService().findBatchInfo(batchInfo.getId());
			BatchLog batchLog = new BatchLog();
			batchLog.setBatchInfo(batchInfo);
			batchLog.setMessage(message);
			batchLog.setError(true);
			CoreServiceLocator.getSystemService().createBatchLog(batchLog);
			ThreadProperties.forcedCommit();
		}
	}
	
	protected final void log(String message) {
		if ((this.persistRunResults()) && (!this.logOnlyErrors())) {
			ThreadProperties.forcedCommitAndNewTransaction();
			batchInfo = CoreServiceLocator.getSystemService().findBatchInfo(batchInfo.getId());
			BatchLog batchLog = new BatchLog();
			batchLog.setBatchInfo(batchInfo);
			batchLog.setMessage(message);
			batchLog.setError(false);
			CoreServiceLocator.getSystemService().createBatchLog(batchLog);
			ThreadProperties.forcedCommitAndNewTransaction();
		}
	}
	
}
